import rough from 'roughjs'
import { select, selectAll } from 'd3-selection'
import mitter from '@/mitt'
import { generateRandomId, getTargetDataById, debounce } from '../utils'
import { getClockwiseAngle, getNearestXPosition } from '../utils/math'
import { getNodeRelationPathPoints } from '../utils/node'
import relationLineShape from '../relation-line-shape'
import markerShape from '../marker-shape/relation'

let rc = null
let svg = null
let container = null
let drawRelationIng = false
let pointControlName = null
let relationNodeSubject = null
let shapeTotalLength = 0
let shapePathStartPosition = null
let shapeCenterPosition = null
let topicNodeData = null
let topicShapeNode = null
const shapePoints = []
const lineRoughOptions = {
  seed: 24,
  bowing: 1.5,
  roughness: 1.2
}

export function graphRelationContainer (svgEl, mindContainer) {
  svg = svgEl
  rc = rough.svg(mindContainer)
  container = mindContainer.append('g').attr('class', 'mind-map-relationbox')
  return container
}

/**
 * 根据起始点和结束点以及两个控制点绘制三次贝塞尔曲线
 * @param {*} relationId
 * @param {*} relationText
 * @param {*} source
 * @param {*} target
 * @param {*} style
 */
export function graphRealRealtionPath (relationId, relationText, source, target, relationStyle) {
  try {
    const { start, end, c1, c2 } = getNodeRelationPathPoints(source, target, relationStyle.lineShape)
    container.append('g').attr('id', `relation-${relationId}`).attr('data-id', `${source.id}-${target.id}`)
      .append('path')
      .attr('class', 'real-relation-path')
      .attr('stroke', relationStyle.lineColor)
      .attr('stroke-width', relationStyle.lineWidth)
      .attr('fill', 'none')
      .attr('marker-end', `url(#end-${relationId})`)
      .attr('marker-start', `url(#start-${relationId})`)
      .attr('stroke-dasharray', relationStyle.lineStyle.includes('dashed') ? `${Math.max(6, relationStyle.lineWidth * 3)}, ${Math.max(5, relationStyle.lineWidth)}` : null)
      .attr('d', () => {
        const isRough = relationStyle.lineStyle.includes('rough')
        const path = relationLineShape[relationStyle.lineShape](start, end, c1, c2)
        if (!isRough) return path
        const node = rc.path(path, lineRoughOptions)
        return select(node).select('path').attr('d')
      })
      // 控制点绘制
      .each(function () {
        const lines = [{ move: start, to: c1 }, { move: end, to: c2 }]
        const circles = [start, end]
        const rects = [c1, c2]
        const controlModelNode = select(this.parentNode).append('g').attr('class', 'controller-model')
          .on('mouseenter', function () {
            select(this.parentNode).raise()
            select(this).select('.active-real-relation-path').attr('opacity', 0.4)
            select(this).selectAll('line, rect, circle').style('display', 'block')
          })
          .on('mouseleave', function () {
            if (!select(this).classed('active-relation')) {
              select(this).select('.active-real-relation-path').attr('opacity', 0)
              select(this).selectAll('line, rect, circle').style('display', 'none')
            }
          })
          .on('click', function (event) {
            event.stopPropagation()
            relationNodeHandlerClick.call(this)
            mitter.emit('relation-node-handler-click')
          })
          .on('contextmenu', function () {
            relationNodeHandlerClick.call(this)
            mitter.emit('relation-node-handler-click')
          })
          .on('dblclick', function (event) {
            mitter.emit('relation-node-dblclick', { event, _this: this })
          })
          .datum({ relationId, relationText, source, target, relationStyle })
        relationStyle.lineShape !== 'straight' && controlModelNode.selectAll('line')
          .data(lines)
          .enter()
          .append('line')
          .attr('class', (_, i) => i === 0 ? 'start-line' : 'end-line')
          .attr('x1', d => d.move.x)
          .attr('y1', d => d.move.y)
          .attr('x2', d => d.to.x)
          .attr('y2', d => d.to.y)
          .attr('stroke', '#2ebaff')
          .attr('stroke-width', 2)
          .attr('style', 'display: none')
        controlModelNode.append('path')
          .attr('class', 'active-real-relation-path')
          .attr('d', relationLineShape[relationStyle.lineShape](start, end, c1, c2))
          .attr('stroke', '#62ceff')
          .attr('stroke-width', relationStyle.lineWidth + 6)
          .attr('opacity', 0)
          .attr('fill', 'none')
        controlModelNode.selectAll('circle')
          .data(circles)
          .enter()
          .append('circle')
          .attr('class', (_, i) => i === 0 ? 'start-circle' : 'end-circle')
          .attr('cx', d => d.x)
          .attr('cy', d => d.y)
          .attr('r', 3.2)
          .attr('stroke-width', 2)
          .attr('stroke', '#2ebaff')
          .attr('fill', '#fff')
          .attr('style', 'display: none;')
          .on('mousedown', relationControlPointClick)
        relationStyle.lineShape !== 'straight' && controlModelNode.selectAll('rect')
          .data(rects)
          .enter()
          .append('rect')
          .attr('class', (_, i) => i === 0 ? 'start-rect' : 'end-rect')
          .attr('x', d => d.x - 3.5)
          .attr('y', d => d.y - 3.5)
          .attr('width', 7)
          .attr('height', 7)
          .attr('stroke-width', 2)
          .attr('stroke', '#2ebaff')
          .attr('fill', '#ffffff')
          .attr('style', 'display: none;')
          .on('mousedown', relationControlPointClick)
      })
      .each(function () {
        const activePathNode = select(this.parentNode).select('.active-real-relation-path').node()
        const pathLength = activePathNode.getTotalLength()
        const textPos = activePathNode.getPointAtLength(pathLength / 2)
        select(this.parentNode)
          .append('text')
          .text(relationText)
          .attr('fill', relationStyle.textColor)
          .attr('x', textPos.x)
          .attr('y', textPos.y)
          .attr('font-size', relationStyle.fontSize)
          .attr('font-family', relationStyle.fontFamily)
          .attr('text-anchor', 'middle')
          .attr('dominant-baseline', 'ideographic')
          .attr('pointer-events', 'none')
      })
      // marker-end、arker-start绘制
      .each(function () {
        const { startPointShape: markStart, endPointShape: markEnd } = relationStyle
        select(this.parentNode).append('defs').selectAll('marker').data(
          [
            { ...markerShape[markStart].start, name: 'start' },
            { ...markerShape[markEnd].end, name: 'end' }
          ]
        )
          .enter()
          .append('marker')
          .attr('id', d => `${d.name}-${relationId}`)
          .attr('viewBox', d => d.viewBox)
          .attr('refX', d => d.refX)
          .attr('refY', d => d.refY)
          .attr('markerUnits', 'strokeWidth')
          .attr('markerWidth', d => d.markerWidth)
          .attr('markerHeight', d => d.markerHeight)
          .attr('orient', 'auto')
          .attr('fill', relationStyle.lineColor)
          .append('path')
          .attr('d', d => d.d)
      })
  } catch (error) {
    console.warn('The node of the relational connection could not be found')
  }
}

function relationControlPointClick (event) {
  event.stopPropagation()
  relationNodeHandlerClick.call(this.parentNode)
  pointControlName = select(this).attr('class')
  const { source, target } = relationNodeSubject
  const touchName = pointControlName.split('-')[0]
  const keyName = touchName === 'start' ? 'source' : 'target'
  topicNodeData = select(`#${keyName === 'source' ? source.id : target.id}`).datum()
  topicShapeNode = select(`#${keyName === 'source' ? source.id : target.id}`).select('.invalid-path, .valid-path').node()
  shapeTotalLength = topicShapeNode.getTotalLength()
  shapePathStartPosition = topicShapeNode.getPointAtLength(0)
  shapeCenterPosition = { x: topicNodeData.x + topicNodeData.width / 2, y: topicNodeData.y + topicNodeData.height / 2 }
  shapePoints.length = 0
  for (let i = 0; i < shapeTotalLength; i++) {
    shapePoints.push(topicShapeNode.getPointAtLength(i))
  }
}

function relationNodeHandlerClick () {
  if (select(this).classed('active-relation')) return
  removeRelationNodeHighLight()
  select(this).classed('active-relation', true)
  relationNodeSubject = select(this).datum()
  mitter.emit('get-relation-style', relationNodeSubject.relationStyle)
}

/**
 * 批量绘制关系连线
 * @param {*} nodes
 */
export function bacthDrawRealRelationPath (nodes) {
  container.selectAll('g').remove()
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i].data
    for (let k = 0; k < node.relations.length; k++) {
      const { relationId, relationText, sourceInfo, targetInfo, style } = node.relations[k]
      graphRealRealtionPath(relationId, relationText, sourceInfo, targetInfo, style)
    }
  }
}

/**
 * 主题之间虚拟关系连接绘制
 * @param {*} relationStyle
 */
export function renderVirtualRelationPath (relationStyle) {
  container.select('.virtual-relation-path').remove()
  selectAll('#triangle-downstream, #triangle-downstream-start').attr('fill', relationStyle.lineColor)
  container
    .append('path')
    .attr('class', 'virtual-relation-path')
    .attr('stroke-dasharray', relationStyle.lineStyle.includes('dashed') ? `${Math.max(6, relationStyle.lineWidth * 2)}, ${Math.max(5, relationStyle.lineWidth)}` : null)
    .attr('stroke', relationStyle.lineColor)
    .attr('stroke-width', relationStyle.lineWidth)
    .attr('fill', 'none')
    .style('pointer-events', 'none')
    .attr('marker-end', 'url(#triangle-downstream)')
    .attr('marker-start', 'url(#triangle-downstream-start)')
}

/**
 * 绘制关系连接虚拟连线的状态下在鼠标移动的时候更新渲染连线
 * @param {*} event
 * @param {*} eventTransform
 * @returns
 */
export function updateRenderVirtualRelationPath (event, eventTransform) {
  if (!drawRelationIng) return
  const { x1, y1 } = againCalcuPosFromScale(event, eventTransform)
  const clockwiseAngle = getClockwiseAngle(
    shapePathStartPosition,
    shapeCenterPosition,
    { x: x1, y: y1 }
  )
  const kk = clockwiseAngle / 360 * shapeTotalLength
  const shape = topicNodeData.style.shape
  const position = topicShapeNode.getPointAtLength(['boom', 'heart'].includes(shape) ? shapeTotalLength - kk : kk)
  const unit = position.x < x1 ? 1 : -1
  container
    .select('.virtual-relation-path')
    .datum({ position, unit, sourceData: topicNodeData })
    .attr('d', () => {
      return `M${position.x} ${position.y}, C ${position.x + 100 * unit} ${position.y} ${x1 - 100 * unit} ${y1} ${x1} ${y1}`
    })
}

/**
 * 根据画布当前的缩放成绩重新计算鼠标移动的坐标点
 * @param {*} event
 * @param {*} eventTransform
 * @returns
 */
export function againCalcuPosFromScale (event, eventTransform) {
  const { x, y } = event
  const { x: tx, y: ty, k } = eventTransform
  return { x1: (x - tx) / k, y1: (y - ty) / k }
}

/**
 * 移除关系连线高亮
 */
export function removeRelationNodeHighLight () {
  container
    .selectAll('.active-relation')
    .classed('active-relation', false)
    .select('.active-real-relation-path').attr('opacity', 0)
    .each(function () {
      select(this.parentNode)
        .selectAll('line, rect, circle').style('display', 'none')
    })
  relationNodeSubject = null
  mitter.emit('get-relation-style', null)
  exitDrawRelation()
}

/**
 * 拖动控制点和断点更新连接线绘制
 * @param {*} relationId
 * @param {*} source
 * @param {*} target
 */
export function updateRelationElementPos (relationId, source, target) {
  const { start, end, c1, c2 } = getNodeRelationPathPoints(source, target, relationNodeSubject.relationStyle.lineShape)
  select(`#relation-${relationId}`)
    .select('.real-relation-path')
    .attr('d', () => {
      const isRough = relationNodeSubject.relationStyle.lineStyle.includes('rough')
      const path = relationLineShape[relationNodeSubject.relationStyle.lineShape](start, end, c1, c2)
      if (!isRough) return path
      const node = rc.path(path, lineRoughOptions)
      return select(node).select('path').attr('d')
    })
  const controllerModeNode = select(`#relation-${relationId}`).select('.controller-model')
  controllerModeNode
    .selectAll('rect')
    .data([c1, c2])
    .attr('x', d => d.x - 3.5)
    .attr('y', d => d.y - 3.5)
  controllerModeNode
    .selectAll('circle')
    .data([start, end])
    .attr('cx', d => d.x).attr('cy', d => d.y)
  controllerModeNode
    .select('.active-real-relation-path')
    .attr('d', relationLineShape[relationNodeSubject.relationStyle.lineShape](start, end, c1, c2))
  controllerModeNode
    .selectAll('line')
    .data([
      { move: start, to: c1 },
      { move: end, to: c2 }
    ])
    .attr('x1', d => d.move.x)
    .attr('y1', d => d.move.y)
    .attr('x2', d => d.to.x)
    .attr('y2', d => d.to.y)
  const pathElement = select(`#relation-${relationId}`).select('.active-real-relation-path').node()
  const pathLength = pathElement.getTotalLength()
  const textPos = pathElement.getPointAtLength(pathLength / 2)
  select(`#relation-${relationId}`).select('text').attr('x', textPos.x).attr('y', textPos.y)
  svg.classed('grabbing', true)
}

/**
 * 插入关系联系时虚拟连线绘制
 * @param {*} id
 * @param {*} relationStyle
 */
export function insertTopicRelation (id, relationStyle) {
  drawRelationIng = true
  topicShapeNode = select(`#${id}`).select('.invalid-path, .valid-path').node()
  shapeTotalLength = topicShapeNode.getTotalLength()
  shapePathStartPosition = topicShapeNode.getPointAtLength(0)
  topicNodeData = select(`#${id}`).datum()
  shapeCenterPosition = { x: topicNodeData.x + topicNodeData.width / 2, y: topicNodeData.y + topicNodeData.height / 2 }
  renderVirtualRelationPath(relationStyle)
}

/**
 * 节点关系连线上的控制点拖动
 * @param {*} event
 * @param {*} eventTransform
 */
function relationPathRectControlMove (event, eventTransform) {
  const touchName = pointControlName.split('-')[0]
  const keyName = touchName === 'start' ? 'source' : 'target'
  const reverseKeyName = touchName === 'start' ? 'target' : 'source'
  const { x1, y1 } = againCalcuPosFromScale(event, eventTransform)
  if (relationNodeSubject.relationStyle?.lineShape === 'zigzag') {
    const { start, end, c1, c2 } = getNodeRelationPathPoints(relationNodeSubject.source, relationNodeSubject.target, relationNodeSubject.relationStyle.lineShape)
    const sourcePointdata = keyName === 'source' ? end : start
    let isHorizontal = c1.y.toFixed(2) === c2.y.toFixed(2)
    let pointAtShape = getNearestXPosition({ x: x1, y: y1 }, shapePoints, isHorizontal ? 'x' : 'y')
    if (
      (isHorizontal ? x1 : y1) - 50 > topicNodeData[isHorizontal ? 'x' : 'y'] + topicNodeData[isHorizontal ? 'width' : 'height'] ||
      (isHorizontal ? x1 : y1) + 50 < topicNodeData[isHorizontal ? 'x' : 'y']
    ) {
      // 如果x轴方向上控制点到端点的距离大于y轴方向上的距离则认为两个控制点之间的连线的竖直方向的，反之则为水平方向
      if (Math.abs(x1 - sourcePointdata.x) > Math.abs(y1 - sourcePointdata.y)) {
        // 两个控制点处于竖直方向
        pointAtShape = getNearestXPosition({ x: x1, y: y1 }, shapePoints, 'y')
        relationNodeSubject[keyName].point.x = pointAtShape.x - topicNodeData.x
        relationNodeSubject[keyName].point.y = pointAtShape.y - topicNodeData.y
        relationNodeSubject[keyName].controller.x = x1 - pointAtShape.x
        relationNodeSubject[keyName].controller.y = 0
        relationNodeSubject[reverseKeyName].controller.x += x1 - c1.x
        relationNodeSubject[reverseKeyName].controller.y = 0
        isHorizontal = false
      } else {
        // 两个控制点处于水平方向
        pointAtShape = getNearestXPosition({ x: x1, y: y1 }, shapePoints, 'x')
        relationNodeSubject[keyName].point.y = pointAtShape.y - topicNodeData.y
        relationNodeSubject[keyName].point.x = pointAtShape.x - topicNodeData.x
        relationNodeSubject[keyName].controller.y = y1 - pointAtShape.y
        relationNodeSubject[keyName].controller.x = 0
        relationNodeSubject[reverseKeyName].controller.y += y1 - c1.y
        relationNodeSubject[reverseKeyName].controller.x = 0
        isHorizontal = true
      }
    }
    const mainPosKeyName = isHorizontal ? 'x' : 'y'
    const vicePosKeyName = isHorizontal ? 'y' : 'x'
    relationNodeSubject[keyName].point[mainPosKeyName] = pointAtShape[mainPosKeyName] - topicNodeData[mainPosKeyName]
    relationNodeSubject[keyName].point[vicePosKeyName] = pointAtShape[vicePosKeyName] - topicNodeData[vicePosKeyName]
    relationNodeSubject[keyName].controller[vicePosKeyName] = (mainPosKeyName === 'x' ? y1 : x1) - pointAtShape[vicePosKeyName]
    relationNodeSubject[reverseKeyName].controller[vicePosKeyName] = (mainPosKeyName === 'x' ? y1 : x1) - sourcePointdata[vicePosKeyName]
  } else {
    const k = eventTransform.k
    relationNodeSubject[keyName].controller.x += event.movementX / k
    relationNodeSubject[keyName].controller.y += event.movementY / k
  }
  const { relationId, source, target } = relationNodeSubject
  try {
    updateRelationElementPos(relationId, source, target)
  } catch (error) {
    console.warn('The node of the relational connection could not be found')
  }
}

/**
 * 节点关系连线两头端点拖动
 * @param {*} event
 * @param {*} eventTransform
 */
function relationPathCircleControlMove (event, eventTransform) {
  const { x1, y1 } = againCalcuPosFromScale(event, eventTransform)
  const clockwiseAngle = getClockwiseAngle(
    shapePathStartPosition,
    shapeCenterPosition,
    { x: x1, y: y1 }
  )
  const touchName = pointControlName.split('-')[0]
  const keyName = touchName === 'start' ? 'source' : 'target'
  const reverseKeyName = touchName === 'start' ? 'target' : 'source'
  const { relationId, source, target } = relationNodeSubject
  const kk = clockwiseAngle / 360 * shapeTotalLength
  const shape = topicNodeData.style.shape
  const position = topicShapeNode.getPointAtLength(['boom', 'heart'].includes(shape) ? shapeTotalLength - kk : kk)
  const { c1, c2 } = getNodeRelationPathPoints(relationNodeSubject.source, relationNodeSubject.target, relationNodeSubject.relationStyle.lineShape)
  const isHorizontal = c1.y.toFixed(2) === c2.y.toFixed(2)
  const keyPosName = isHorizontal ? 'x' : 'y'
  const reverseKeyPosName = isHorizontal ? 'y' : 'x'
  relationNodeSubject[keyName].point[keyPosName] = position[keyPosName] - topicNodeData[keyPosName]
  const positionDiff = position[reverseKeyPosName] - topicNodeData[reverseKeyPosName] - relationNodeSubject[keyName].point[reverseKeyPosName]
  relationNodeSubject[keyName].point[reverseKeyPosName] += positionDiff
  relationNodeSubject[reverseKeyName].controller[reverseKeyPosName] += positionDiff
  try {
    updateRelationElementPos(relationId, source, target)
  } catch (error) {
    console.warn('The node of the relational connection could not be found')
  }
}

/**
 * 拖动关系连线的控制点或者两头端点
 * @param {*} event
 * @param {*} eventTransform
 * @returns
 */
export function dragRelationControlMove (event, eventTransform) {
  if (!pointControlName || !relationNodeSubject) return
  if (pointControlName.includes('rect')) {
    relationPathRectControlMove(event, eventTransform)
  } else if (pointControlName.includes('circle')) {
    relationPathCircleControlMove(event, eventTransform)
  }
}

export function drawRelationPathEnd (_this, root, relationStyle, callback) {
  if (!drawRelationIng) return
  const relationId = generateRandomId()
  const source = select('.virtual-relation-path').datum()
  const { position, unit, sourceData } = source
  const relationText = '关系'
  const sourceInfo = {
    id: sourceData.data._id,
    point: { x: position.x - sourceData.x, y: position.y - sourceData.y },
    controller: { x: unit * 100, y: 0 }
  }

  topicShapeNode = select(_this).select('.invalid-path, .valid-path').node()
  topicNodeData = select(_this).datum()
  shapeCenterPosition = {
    x: topicNodeData.x + topicNodeData.width / 2,
    y: topicNodeData.y + topicNodeData.height / 2
  }
  shapeTotalLength = topicShapeNode.getTotalLength()
  const shape = topicNodeData.style.shape
  shapePathStartPosition = topicShapeNode.getPointAtLength(0)
  const clockwiseAngle = getClockwiseAngle(
    shapePathStartPosition,
    shapeCenterPosition,
    position
  )
  const kk = clockwiseAngle / 360 * shapeTotalLength
  const targetPosition = topicShapeNode.getPointAtLength(['boom', 'heart'].includes(shape) ? shapeTotalLength - kk : kk)
  const targetInfo = {
    id: topicNodeData.data._id,
    point: { x: targetPosition.x - topicNodeData.x, y: targetPosition.y - topicNodeData.y },
    controller: { x: -unit * 100, y: 0 }
  }
  graphRealRealtionPath(relationId, relationText, sourceInfo, targetInfo, relationStyle)
  const sourceNodeData = getTargetDataById(root, sourceData.data._id)
  sourceNodeData.relations = [...(sourceNodeData.relations || []), {
    relationId, sourceInfo, targetInfo, relationText
  }]
  exitDrawRelation()
  callback && callback()
}

export function drawRelationControlEnd (root, callback) {
  if (!pointControlName) return
  try {
    const { relationId, source, target } = relationNodeSubject
    const sourceId = source.id
    const sourceNodeData = getTargetDataById(root, sourceId)
    const relations = sourceNodeData.relations
    const idx = relations.findIndex(o => o.relationId === relationId)
    const targetRelationItem = relations[idx]
    if (pointControlName.includes('start')) {
      relations.splice(idx, 1, { ...targetRelationItem, sourceInfo: source, targetInfo: target })
    } else {
      relations.splice(idx, 1, { ...targetRelationItem, sourceInfo: source, targetInfo: target })
    }
    callback && callback()
  } catch (error) {
    console.warn('drag node error')
  }
  pointControlName = null
  svg.classed('grabbing', false)
}

export function deleteRelationPath (root, callback) {
  if (relationNodeSubject) {
    const sourceId = relationNodeSubject.source.id
    const relationId = relationNodeSubject.relationId
    const sourceData = getTargetDataById(root, sourceId)
    const relations = sourceData.relations || []
    const idx = relations.findIndex(o => o.relationId === relationId)
    relations.splice(idx, 1)
    select(`#relation-${relationId}`).remove()
    removeRelationNodeHighLight()
    callback && callback()
  }
}

export function exitDrawRelation () {
  if (drawRelationIng) {
    drawRelationIng = false
    select('.mind-map-relationbox').select('.virtual-relation-path').remove()
  }
}

/**
 * 防抖更新数据
 */
const debounceUpdateData = debounce((styleName, styleValue, relationNodeSubject, root, callback) => {
  const { relationId, relationText, relationStyle, source, target } = relationNodeSubject
  Object.assign(relationStyle, { [styleName]: styleValue })
  select(`#relation-${relationId}`).select('.controller-model').datum({
    relationId,
    relationText,
    source,
    target,
    relationStyle
  })
  const sourceId = source.id
  const sourceNodeData = getTargetDataById(root, sourceId)
  const relations = sourceNodeData.relations
  const idx = relations.findIndex(o => o.relationId === relationId)
  const targetRelationItem = relations[idx]
  targetRelationItem.style = {
    ...targetRelationItem.style,
    [styleName]: styleValue
  }
  targetRelationItem.sourceInfo = source
  targetRelationItem.targetInfo = target
  callback && callback()
}, 300)

/**
 * 关系连线样式更新
 * @param {*} styleName
 * @param {*} styleValue
 */
export function updateRelationStyle (styleName, styleValue, root, callback) {
  if (!relationNodeSubject) return
  const { relationId, relationStyle, source, target } = relationNodeSubject
  if (styleName === 'lineStyle') {
    select(`#relation-${relationId}`)
      .select('.real-relation-path')
      .attr('stroke-dasharray', styleValue.includes('solid') ? null : `${Math.max(6, relationStyle.lineWidth * 3)}, ${Math.max(5, relationStyle.lineWidth)}`)
      .attr('d', () => {
        const { start, end, c1, c2 } = getNodeRelationPathPoints(source, target, relationStyle.lineShape)
        const isRough = styleValue.includes('rough')
        const path = relationLineShape[relationStyle.lineShape](start, end, c1, c2)
        if (!isRough) return path
        const node = rc.path(path, lineRoughOptions)
        return select(node).select('path').attr('d')
      })
  } else if (styleName === 'lineWidth') {
    select(`#relation-${relationId}`).select('.real-relation-path').attr('stroke-width', styleValue)
      .attr('stroke-dasharray', function () {
        if (select(this).attr('stroke-dasharray')) return `${Math.max(6, styleValue * 3)}, ${Math.max(5, styleValue)}`
        return null
      })
    select(`#relation-${relationId}`).select('.active-real-relation-path').attr('stroke-width', styleValue + 6)
  } else if (styleName === 'fontFamily') {
    select(`#relation-${relationId} > text`).attr('font-family', styleValue)
  } else if (styleName === 'fontSize') {
    select(`#relation-${relationId} > text`).attr('font-size', styleValue)
  } else if (styleName === 'lineColor') {
    select(`#relation-${relationId}`).select('.real-relation-path').attr('stroke', styleValue)
    select(`#relation-${relationId}`).selectAll('marker').attr('fill', styleValue)
  } else if (styleName === 'textColor') {
    select(`#relation-${relationId} > text`).attr('fill', styleValue)
  } else if (['startPointShape', 'endPointShape'].includes(styleName)) {
    const key = styleName.replace('PointShape', '')
    const mark = select(`#${key}-${relationId}`)
    mark.attr('refX', markerShape[styleValue][key].refX)
      .attr('viewBox', markerShape[styleValue][key].viewBox)
      .attr('refX', markerShape[styleValue][key].refX)
      .attr('refY', markerShape[styleValue][key].refY)
      .attr('markerWidth', markerShape[styleValue][key].markerWidth)
      .attr('markerHeight', markerShape[styleValue][key].markerHeight)
      .select('path').attr('d', markerShape[styleValue][key].d)
  } else if (styleName === 'lineShape') {
    const { start, end, c1, c2 } = getNodeRelationPathPoints(source, target, styleValue)
    if (styleValue === 'zigzag') {
      // 两个控制点之间的x轴坐标距离大于等于y轴之间的距离则认为是水平方向
      const isHorizontal = Math.abs(c2.x - c1.x) >= Math.abs(c2.y - c1.y)
      const keyPosName = isHorizontal ? 'x' : 'y'
      const reverseKeyPosName = isHorizontal ? 'y' : 'x'
      const c1Spacing = start[keyPosName] - c1[keyPosName]
      const c2Spacing = end[keyPosName] - c2[keyPosName]
      const c3Spacing = c1[reverseKeyPosName] - c2[reverseKeyPosName]
      c1[keyPosName] += c1Spacing
      c2[keyPosName] += c2Spacing
      c2[reverseKeyPosName] += c3Spacing
      relationNodeSubject.source.controller[keyPosName] += c1Spacing
      relationNodeSubject.target.controller[keyPosName] += c2Spacing
      relationNodeSubject.target.controller[reverseKeyPosName] += c3Spacing
    }
    if (styleValue === 'straight') {
      select(`#relation-${relationId}`).select('.controller-model').selectAll('line, rect').remove()
    } else {
      const rects = [c1, c2]
      const lines = [{ move: start, to: c1 }, { move: end, to: c2 }]
      if (select(`#relation-${relationId}`).select('.controller-model').select('line').empty()) {
        select(`#relation-${relationId}`).select('.controller-model').selectAll('line')
          .data(lines).enter().append('line')
          .attr('class', (_, i) => i === 0 ? 'start-line' : 'end-line')
          .attr('x1', d => d.move.x).attr('y1', d => d.move.y).attr('x2', d => d.to.x).attr('y2', d => d.to.y)
          .attr('stroke', '#2ebaff')
          .attr('stroke-width', 2)
          .attr('style', 'display: block')
        select(`#relation-${relationId}`).select('.controller-model').selectAll('rect')
          .data(rects).enter().append('rect')
          .attr('class', (_, i) => i === 0 ? 'start-rect' : 'end-rect')
          .attr('x', d => d.x - 3.5).attr('y', d => d.y - 3.5).attr('width', 7).attr('height', 7)
          .attr('stroke-width', 2)
          .attr('stroke', '#2ebaff')
          .attr('fill', '#ffffff')
          .attr('style', 'display: block;')
          .on('mousedown', relationControlPointClick)
      } else {
        select(`#relation-${relationId}`).select('.controller-model').selectAll('line')
          .data(lines).attr('x1', d => d.move.x).attr('y1', d => d.move.y).attr('x2', d => d.to.x).attr('y2', d => d.to.y)
        select(`#relation-${relationId}`).select('.controller-model').selectAll('rect')
          .data(rects).attr('x', d => d.x - 3.5).attr('y', d => d.y - 3.5).attr('width', 7).attr('height', 7)
      }
    }
    select(`#relation-${relationId}`).select('.real-relation-path')
      .attr('d', () => {
        const isRough = relationStyle.lineStyle.includes('rough')
        const path = relationLineShape[styleValue](start, end, c1, c2)
        if (!isRough) return path
        const node = rc.path(path, lineRoughOptions)
        return select(node).select('path').attr('d')
      })
    select(`#relation-${relationId}`).select('.active-real-relation-path').attr('d', relationLineShape[styleValue](start, end, c1, c2))
    const pathElement = select(`#relation-${relationId}`).select('.active-real-relation-path').node()
    const pathLength = pathElement.getTotalLength()
    const textPos = pathElement.getPointAtLength(pathLength / 2)
    select(`#relation-${relationId}`).select('text').attr('x', textPos.x).attr('y', textPos.y)
  }

  debounceUpdateData(styleName, styleValue, relationNodeSubject, root, callback)
}

/**
 * 关系连线全量style更新
 * @param {*} root
 * @param {*} style
 * @param {*} callback
 */
export function updateRelationFullStyle (root, style, callback) {
  if (!relationNodeSubject) return
  const sourceData = getTargetDataById(root, relationNodeSubject.source.id)
  const relations = sourceData.relations
  const targetRelation = relations.find(o => o.relationId === relationNodeSubject.relationId)
  targetRelation.style = style
  callback && callback()
}
